namespace C4F.DevKit.Bluetooth
{
    namespace ServiceAndDeviceDiscovery
    {
        #region Using Directives
        using System;
        using System.Collections.Generic;
        using System.Text;
        using System.Text.RegularExpressions;
        using System.Runtime.InteropServices;
        using System.IO;
        using Resources;
        #endregion

        /// <summary>
        /// An abstract class which represents the Bluetooth service and 
        /// contains general methods and properties which are 
        /// required for communicating with client application
        /// </summary>
        public abstract class Service
        {
            #region Protected Fields
            /// <summary>
            /// Queryset structure holds the information related 
            /// to the service.
            /// </summary>
            protected QuerySet wsaQuerySet;
            /// <summary>
            /// Represents the description of the service.
            /// </summary>
            protected string description;
            /// <summary>
            /// Represents the id of the service.
            /// </summary>
            protected ServiceId id;
            /// <summary>
            /// Represents the name of the service.
            /// </summary>
            protected string name;
            /// <summary>
            /// Represents the service record of the service.            
            /// </summary>
            protected byte[] serviceRecord;
            /// <summary>
            /// Represents the type of the service.
            /// </summary>
            protected ServiceType type;

            #endregion

            #region Internal Functions
            /// <summary>
            /// Initializes a BluetoothService object.
            /// Used for initializing a LocalService.
            /// </summary>
            internal Service()
            {

            }
            /// <summary>
            /// Initializes a BluetoothService object.
            /// Used for initializing a RemoteService.
            /// </summary>
            /// <param name="querySet"></param>
            internal Service(QuerySet querySet)
            {                
                this.wsaQuerySet = querySet;

                this.id.ServiceIdType = ServiceIdType.TypeNil;
                this.id.ServiceIdUnion = new ServiceIdUnion();
                this.type = ServiceType.UnknownProfile;

                this.description = GetDescription(this.wsaQuerySet);
                this.name = GetName(this.wsaQuerySet);
                //Note: A call to GetServiceRecord also extracts the 
                //service id and type values from the service record.
                this.serviceRecord = GetServiceRecord(this.wsaQuerySet);
                
            }
            #endregion

            #region Private Functions
            /// <summary>
            ///Extracts the service description.
            /// </summary>
            /// <param name="querySet">Queryset structure holds the information
            /// related to the service.</param>
            /// <returns>Returns the description of the service.</returns>
            private string GetDescription(QuerySet querySet)
            {
                string description = string.Empty;

                if (querySet.Comment != null)
                {
                    description =
                     Regex.Replace(querySet.Comment,
                     SDPMessages.PatternForNonPrintChars,
                     string.Empty,
                     RegexOptions.IgnoreCase);
                }
                return description.Trim();

            }
            /// <summary>
            /// Extracts the service name.
            /// </summary>
            /// <param name="querySet">Queryset structure holds the information
            /// related to the service.</param>
            /// <returns>Returns the name of the service.</returns>
            private string GetName(QuerySet querySet)
            {
                string name = string.Empty;

                if (querySet.ServiceInstanceName != null)
                {
                    name = Regex.Replace(querySet.ServiceInstanceName,
                     SDPMessages.PatternForNonPrintChars,
                     string.Empty,
                     RegexOptions.IgnoreCase);
                }

                return name.Trim();
            }
            /// <summary>
            /// Extracts the service record.
            /// </summary>
            /// <param name="querySet">Queryset structure holds the information
            /// related to the service.</param>
            /// <returns>Returns the service record of the service.</returns>
            private byte[] GetServiceRecord(QuerySet querySet)
            {
                try
                {

                    Blob blob = new Blob();
                    blob = (Blob)Marshal.PtrToStructure(querySet.Blob,
                                                        typeof(Blob));

                    byte[] byteBlobData = null;

                    if (blob.Size != 0)
                    {
                        byteBlobData = new byte[blob.Size];

                        Marshal.Copy(querySet.Blob, byteBlobData, 0,
                                     (int)blob.Size);

                        //declare call back for BluetoothSdpEnumAttributes function
                        BluetoothHelper.CallbackEventHandler calbckEventHandler =
                            new BluetoothHelper.CallbackEventHandler(ParseAttribute);

                        //Enumerates through the SDP record stream,
                        //and calls the ParseAttribute function for each attribute 
                        //in the record.

                        if (!BluetoothHelper.BluetoothSdpEnumAttributes(
                                                                blob.BlobData,
                                                                blob.Size,
                                                                ParseAttribute,
                                                                new IntPtr()))
                        {
                            return null;
                        }
                        else
                            return byteBlobData;
                    }
                    else
                        return byteBlobData;
                }
                catch
                {
                    return null;
                }

            }
            /// <summary>
            /// Callback function for extracting attributes of the service 
            /// from the service record.
            /// </summary>
            /// <param name="attribId">Identifies the current attribute in the
            /// service record.</param>
            /// <param name="valueStream">Represents the value associated with 
            /// the attribute</param>
            /// <param name="streamSize">size in bytes of valueStream</param>
            /// <param name="param">Optional.The context passed in from a previous call 
            /// to the BluetoothSdpEnumAttributes function</param>
            /// <returns>Returns TRUE when the enumeration continues to the next 
            /// attribute identifier found in the stream. Return FALSE
            /// when enumeration of the record attribute identifiers should 
            /// immediately stop.</returns>
            private bool ParseAttribute(uint attribId, IntPtr valueStream,
                                        uint streamSize, IntPtr param)
            {

                SdpElementData element = new SdpElementData();

                int result =
                    BluetoothHelper.BluetoothSdpGetElementData(valueStream,
                                                               streamSize,
                                                               out element);

                if (result != 0)
                    BluetoothHelper.GetError();

                switch (element.Type)
                {
                    case SdpType.SdpTypeSequence:
                        //Represents the service class id i.e. type of the service
                        if (attribId == 1)
                        {
                            if (element.Data.SequenceValue.Length > 0)
                            {
                                byte[] sequenceData =
                                    new byte[element.Data.SequenceValue.Length];

                                Marshal.Copy(element.Data.SequenceValue.Value,
                                       sequenceData,
                                       0,
                                       (int)element.Data.SequenceValue.Length);

                                int length = 0;
                                string hexValue = string.Empty;

                                if (sequenceData[0] == 53)
                                {
                                    int sizeDescriptor = sequenceData[1];

                                    //size of type is represented by 2 bytes
                                   
                                    if (sequenceData[2] == 25)
                                        length = 2;

                                    else
                                     //size of type is represented by length variable.
                                        length = sequenceData.Length - 3;

                                    //Convert the value to hex 
                                    for (int i = 3; i <= length + 2; i++)
                                    {
                                        int inum = sequenceData[i];
                                        hexValue = hexValue
                                                   + String.Format(
                                                     SDPMessages.HexFormatter,
                                                     inum);
                                    }

                                    string serviceType = string.Empty;

                                    //Append the base UUID 
                                    if (length == 2)
                                        serviceType =
                                            SDPMessages.GuidBeginValue +
                                            hexValue +
                                            SDPMessages.GuidEndValue;

                                    else
                                    {
                                        //Convert value to Guid format
                                        hexValue =
                                          hexValue.Insert(8,
                                          SDPMessages.Seperator);
                                        hexValue =
                                          hexValue.Insert(13,
                                          SDPMessages.Seperator);
                                        hexValue =
                                           hexValue.Insert(18,
                                           SDPMessages.Seperator);
                                        hexValue =
                                           hexValue.Insert(23,
                                           SDPMessages.Seperator);

                                        serviceType = hexValue;
                                    }
                                    this.type = new ServiceType(new Guid(serviceType), null);
                                }
                            }
                        }
                        break;
                    case SdpType.SdpTypeUuid:
                        //Represents the service id of the service
                        if (attribId == 3)
                        {
                            switch (element.SpecificType)
                            {
                                case SdpSpecificType.SdpStUuid16:
                                    ushort uuid16Value = element.Data.Uuid16Value;
                                    this.id.ServiceIdType = ServiceIdType.TypeUint16;
                                    this.id.ServiceIdUnion.Uuid16Value = uuid16Value;
                                    break;

                                case SdpSpecificType.SdpStUuid32:

                                    uint uuid32Value = element.Data.Uuid32Value;
                                    this.id.ServiceIdType = ServiceIdType.TypeUint32;
                                    this.id.ServiceIdUnion.Uuid32Value = uuid32Value;
                                    break;

                                case SdpSpecificType.SdpStUuid128:
                                    Guid uuid128Value = element.Data.Uuid128Value;
                                    this.id.ServiceIdType = ServiceIdType.TypeUuid;
                                    this.id.ServiceIdUnion.Uuid128Value = uuid128Value;
                                    break;

                            }
                        }

                        break;
                    default:
                        break;

                }
                return true;
            }
            #endregion

            #region Public Properties
            /// <summary>
            /// Gets the service name.
            /// </summary>
            public string Name
            {
                get
                {
                    return this.name;
                }
            }
            /// <summary>
            /// Gets the service description.
            /// </summary>
            public string Description
            {
                get
                {
                    return this.description;
                }
            }
            /// <summary>
            /// Gets the service record.
            /// </summary>
            public byte[] ServiceRecord
            {
                get
                {

                    return this.serviceRecord;
                }
            }
            /// <summary>
            /// Gets the service type.
            /// </summary>
            public ServiceType Type
            {
                get
                {
                    return this.type;
                }
            }
            /// <summary>
            /// Gets the service id
            /// </summary>
            public ServiceId Id
            {
                get
                {
                    return this.id;

                }
            }
            #endregion

        }
    }
}